home *** CD-ROM | disk | FTP | other *** search
- /* -------------------------------------------------------------------------- */
- /* */
- /* (C) Copyright Creative Technology Ltd 1994-1996. All right reserved */
- /* */
- /* THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY */
- /* KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE */
- /* IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR */
- /* PURPOSE. */
- /* */
- /* You have a royalty-free right to use, modify, reproduce and */
- /* distribute the Sample Files (and/or any modified version) in */
- /* any way you find useful, provided that you agree that */
- /* Creative has no warranty obligations or liability for any Sample Files. */
- /* */
- /*----------------------------------------------------------------------------*/
-
- /*
- * This Program is written using Borland C++ Ver 3.1.
- * To Compile : BCC FMSAMPLE.C
- * ---------------------------------------------------------------------
- *
- * This program is not intended to explain all the aspects of FM sound
- * generation on Sound Blaster cards. The chips are too complicated for
- * that. This program is just to demonstrate how to generate a tone and
- * control the left and right channels. For more information on the FM
- * synthesizer chip, contact Yamaha.
- *
- * Here's a brief description of FM: Each sound is created by two operator
- * cells (called "slots" in the Yamaha documentation), a modulator and a
- * carrier. When FM synthesis was invented, the output value of the
- * modulator affected the frequency of the carrier. In the Yamaha chips, the
- * modulator output actually affects the phase of the carrier instead of
- * frequency, but this has a similar effect.
- *
- * Normally the modulator and carrier would probably be connected in series
- * for complex sounds. For this program, I wanted a pure sine wave, so I
- * connected them in parallel and turned the modulator output down all the
- * way and used just the carrier.
- *
- * Sound Blaster 1.0 - 2.0 cards have one OPL-2 FM synthesis chip at
- * addresses 2x8 and 2x9 (base + 8 and base + 9). Sound Blaster Pro version
- * 1 cards (CT-1330) achieve stereo FM with two OPL-2 chips, one for each
- * speaker. The left channel FM chip is at addresses 2x0 and 2x1. The right
- * is at 2x2 and 2x3. Addresses 2x8 and 2x9 address both chips
- * simultaneously, thus maintaining compatibility with the monaural Sound
- * Blaster cards. The OPL-2 contains 18 operator cells which make up the
- * nine 2-operator channels. Since the CT-1330 SB Pro has two OPL-2 chips,
- * it is therefore capable of generating 9 voices in each speaker.
- *
- * Sound Blaster Pro version 2 (CT-1600) and Sound Blaster 16 cards have one
- * OPL-3 stereo FM chip at addresses 2x0 - 2x3. The OPL-3 is separated into
- * two "banks." Ports 2x0 and 2x1 control bank 0, while 2x2 and 2x3 control
- * bank 1. Each bank can generate nine 2-operator voices. However, when the
- * OPL-3 is reset, it switches into OPL-2 mode. It must be put into OPL-3
- * mode to use the voices in bank 1 or the stereo features. For stereo
- * control, each channel may be sent to the left, the right, or both
- * speakers, controlled by two bits in registers C0H - C8H.
- *
- * The FM chips are controlled through a set of registers. The following
- * table shows how operator cells and channels are related, and the register
- * offsets to use.
- *
- * FUNCTION MODULATOR- -CARRIER-- MODULATOR- -CARRIER-- MODULATOR- -CARRIER--
- * OP CELL 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
- * CHANNEL 1 2 3 1 2 3 4 5 6 4 5 6 7 8 9 7 8 9
- * OFFSET 00 01 02 03 04 05 08 09 0A 0B 0C 0D 10 11 12 13 14 15
- *
- * An example will make the use of this table clearer: suppose you want to
- * set the attenuation of both of the operators of channel 4. The KSL/TOTAL LEVEL
- * registers (which set the attenuation) are 40H - 55H. The modulator for
- * channel 4 is op cell 7, and the carrier for channel 4 is op cell 10. The
- * offsets for the modulator and carrier cells are 08H and 0BH, respectively.
- * Therefore, to set the attenuation of the modulator, you would output a
- * value to register 40H + 08H == 48H, and to set the carrier's attenuation,
- * you would output to register 40H + 0BH == 4BH.
- *
- * In this program, I used just channel 1, so the registers I used were 20H,
- * 40H, 60H, etc., and 23H, 43H, 63H, etc.
- *
- * The frequencies of each channel are controlled with a frequency number and
- * a multiplier. The modulator and carrier of a channel both get the same
- * frequency number, but they may be given different multipliers. Frequency
- * numbers are programmed in registers A0H - A8H (low 8 bits) and B0H - B8H
- * (high 2 bits). Those registers control entire channels (2 operators), not
- * individual operator cells. To turn a note on, the key-on bit in the
- * appropriate channel register is set. Since these registers deal with
- * channels, you do not use the offsets listed in the table above. Instead,
- * add (channel-1) to A0H or B0H. For example, to turn channel 1 on,
- * program the frequency number in registers A0H and B0H, and set the key-on
- * bit to 1 in register B0H. For channel 3, use registers A2H and B2H.
- *
- * Bits 2 - 4 in registers B0H - B8H are the block (octave) number for the
- * channel.
- *
- * Multipliers for each operator cell are programmed through registers 20H -
- * 35H. The table below shows what multiple number to program into the
- * register to get the desired multiplier. The multiple number goes into
- * bits 0 - 3 in the register. Note that it's a bit strange at the end.
- *
- * multiple number multiplier multiple number multiplier
- * 0 1/2 8 8
- * 1 1 9 9
- * 2 2 10 10
- * 3 3 11 10
- * 4 4 12 12
- * 5 5 13 12
- * 6 6 14 15
- * 7 7 15 15
- *
- * This equation shows how to calculate the required frequency number (to
- * program into registers A0H - A8H and B0H - B8H) to get the desired
- * frequency:
- * fn=(long)f * 1048576 / b / m /50000L
- * where f is the frequency in Hz,
- * b is the block (octave) number between 0 and 7 inclusive, and
- * m is the multiple number between 0 and 15 inclusive.
- *
- */
-
-
- #define STEREO // Define this for SBPro CT-1330 or later card.
- #define OPL3 // Also define this for SBPro CT-1600 or later card.
-
-
- #include <stdio.h>
- #include <stdlib.h>
- #include <conio.h>
- #include <ctype.h>
- #include <dos.h>
-
- #define KEYON 0x20 // key-on bit in regs b0 - b8
-
- /* These are offsets from the base I/O address. */
- #define FM 8 // SB (mono) ports (e.g. 228H and 229H)
- #define PROFM1 0 // On CT-1330, this is left OPL-2. On CT-1600 and
- // later cards, it's OPL-3 bank 0.
- #define PROFM2 2 // On CT-1330, this is right OPL-2. On CT-1600 and
- // later cards, it's OPL-3 bank 1.
-
-
-
- #ifdef OPL3
- #define LEFT 0x10
- #define RIGHT 0x20
- #endif
-
-
- unsigned IOport; // Sound Blaster port address
-
-
-
- void mydelay(unsigned long clocks)
- /*
- * "clocks" is clock pulses (at 1.193180 MHz) to elapse, but remember that
- * normally the system timer runs in mode 3, in which it counts down by twos,
- * so delay3(1193180) will only delay half a second.
- *
- * clocks = time * 2386360
- *
- * time = clocks / 2386360
- */
- {
- unsigned long elapsed=0;
- unsigned int last,next,ncopy,diff;
-
- /* Read the counter value. */
- outp(0x43,0); /* want to read timer 0 */
- last=inp(0x40); /* low byte */
- last=~((inp(0x40)<< 8) + last); /* high byte */
-
- do {
- /* Read the counter value. */
- outp(0x43,0); /* want to read timer 0 */
- next=inp(0x40); /* low byte */
- ncopy=next=~((inp(0x40)<< 8) + next); /* high byte */
-
- next-=last; /* this is now number of elapsed clock pulses since last read */
-
- elapsed += next; /* add to total elapsed clock pulses */
- last=ncopy;
- } while (elapsed<clocks);
- }
-
-
-
- int base16(char **str, unsigned *val)
- /* Takes a double pointer to a string, interprets the characters as a
- * base-16 number, and advances the pointer.
- * Returns 0 if successful, 1 if not.
- */
- {
- char c;
- int digit;
- *val = 0;
-
- while ( **str != ' ') {
- c = toupper(**str);
- if (c >= '0' && c <= '9')
- digit = c - '0';
- else if (c >= 'A' && c <= 'F')
- digit = c - 'A' + 10;
- else
- return 1; // error in string
-
- *val = *val * 16 + digit;
- (*str)++;
- }
- return 0;
- }
-
-
-
- int base10(char **str, unsigned *val)
- /* Takes a double pointer to a string, interprets the characters as a
- * base-10 number, and advances the pointer.
- * Returns 0 if successful, 1 if not.
- */
- {
- char c;
- int digit;
- *val = 0;
-
- while ( **str != ' ') {
- c = toupper(**str);
- if (c >= '0' && c <= '9')
- digit = c - '0';
- else
- return 1; // error in string
-
- *val = *val * 10 + digit;
- (*str)++;
- }
- return 0;
- }
-
-
-
- unsigned ReadBlasterEnv(unsigned *port, unsigned *irq, unsigned *dma8,
- unsigned *dma16)
- /* Gets the Blaster environment statement and stores the values in the
- * variables whose addresses were passed to it.
- * Returns:
- * 0 if successful
- * 1 if there was an error reading the port address.
- * 2 if there was an error reading the IRQ number.
- * 3 if there was an error reading the 8-bit DMA channel.
- * 4 if there was an error reading the 16-bit DMA channel.
- */
- {
- char *env;
- unsigned val;
- int digit;
-
- env = getenv("BLASTER");
-
- while (*env) {
- switch(toupper( *(env++) )) {
- case 'A':
- if (base16(&env, port)) // interpret port value as hex
- return 1; // error
- break;
- case 'I':
- if (base10(&env, irq)) // interpret IRQ as decimal
- return 2; // error
- break;
- case 'D':
- if (base10(&env, dma8)) // 8-bit DMA channel is decimal
- return 3;
- break;
- case 'H':
- if (base10(&env, dma16)) // 16-bit DMA channel is decimal
- return 4;
- break;
- default:
- break;
- }
- }
-
- return 0;
- }
-
-
-
- void FMoutput(unsigned port, int reg, int val)
- /* This outputs a value to a specified FM register at a specified FM port. */
- {
- outp(port, reg);
- mydelay(8); /* need to wait 3.3 microsec */
- outp(port+1, val);
- mydelay(55); /* need to wait 23 microsec */
- }
-
-
-
- void fm(int reg, int val)
- /* This function outputs a value to a specified FM register at the Sound
- * Blaster (mono) port address.
- */
- {
- FMoutput(IOport+FM, reg, val);
- }
-
-
- void Profm1(int reg, int val)
- /* This function outputs a value to a specified FM register at the Sound
- * Blaster Pro left FM port address (or OPL-3 bank 0).
- */
- {
- FMoutput(IOport+PROFM1, reg, val);
- }
-
-
- void Profm2(int reg, int val)
- /* This function outputs a value to a specified FM register at the Sound
- * Blaster Pro right FM port address (or OPL-3 bank 1).
- */
- {
- FMoutput(IOport+PROFM2, reg, val);
- }
-
-
-
-
- void main(void)
- {
- int i,val1,val2;
-
- int block,b,m,f,fn;
-
- unsigned dummy;
-
-
- clrscr();
-
- ReadBlasterEnv(&IOport, &dummy, &dummy, &dummy);
-
- #ifdef STEREO
- #ifdef OPL3
- printf("Program compiled for Sound Blaster Pro ver. 2 (CT-1600) and SB16 cards.\n");
- #else
- printf("Program compiled for Sound Blaster Pro ver. 1 (CT-1330) cards.\n");
- #endif
- #else
- printf("Program compiled for Sound Blaster 1.0 - 2.0 cards (monaural).\n");
- #endif
-
-
- fm(1,0); /* must initialize this to zero */
-
- #ifdef OPL3
- Profm2(5, 1); /* set to OPL3 mode, necessary for stereo */
- fm(0xC0,LEFT | RIGHT | 1); /* set both channels, parallel connection */
- #else
- fm(0xC0, 1); /* parallel connection */
- #endif
-
- /***************************************
- * Set parameters for the carrier cell *
- ***************************************/
-
- fm(0x23,0x21); /* no amplitude modulation (D7=0), no vibrato (D6=0),
- * sustained envelope type (D5=1), KSR=0 (D4=0),
- * frequency multiplier=1 (D4-D0=1)
- */
-
- fm(0x43,0x0); /* no volume decrease with pitch (D7-D6=0),
- * no attenuation (D5-D0=0)
- */
-
- fm(0x63,0xff); /* fast attack (D7-D4=0xF) and decay (D3-D0=0xF) */
- fm(0x83,0x05); /* high sustain level (D7-D4=0), slow release rate (D3-D0=5) */
-
-
- /*****************************************
- * Set parameters for the modulator cell *
- *****************************************/
-
- fm(0x20,0x20); /* sustained envelope type, frequency multiplier=0 */
- fm(0x40,0x3f); /* maximum attenuation, no volume decrease with pitch */
-
- /* Since the modulator signal is attenuated as much as possible, these
- * next two values shouldn't have any effect.
- */
- fm(0x60,0x44); /* slow attack and decay */
- fm(0x80,0x05); /* high sustain level, slow release rate */
-
-
- /*************************************************
- * Generate tone from values looked up in table. *
- *************************************************/
-
- printf("440 Hz tone, values looked up in table.\n");
- fm(0xa0,0x41); /* 440 Hz */
- fm(0xb0,0x32); /* 440 Hz, block 0, key on */
-
- getche();
-
- fm(0xb0,0x12); /* key off */
-
-
- /******************************************
- * Generate tone from a calculated value. *
- ******************************************/
-
- printf("440 Hz tone, values calculated.\n");
- block=4; /* choose block=4 and m=1 */
- m=1; /* m is the frequency multiple number */
- f=440; /* want f=440 Hz */
- b= 1 << block;
-
- /* This is the equation to calculate frequency number from frequency. */
-
- fn=(long)f * 1048576 / b / m /50000L;
-
- fm(0x23,0x20 | (m & 0xF)); /* 0x20 sets sustained envelope, low nibble
- * is multiple number
- */
- fm(0xA0,(fn & 0xFF));
- fm(0xB0,((fn >> 8) & 0x3) + (block << 2) | KEYON);
-
- getche();
-
-
- /*********************************************************
- * Generate a range of octaves by changing block number. *
- *********************************************************/
-
- printf("Range of frequencies created by changing block number.\n");
- for (block=0; block<=7; block++) {
- printf("f=%5ld Hz (press Enter)\n",(long)440*(1 << block)/16);
- fm(0xB0,((fn >> 8) & 0x3) + (block << 2) | KEYON);
- getche();
- }
-
-
- /*****************************************************************
- * Generate a range of frequencies by changing frequency number. *
- *****************************************************************/
-
- printf("Range of frequencies created by changing frequency number.\n");
- block=4;
- for (fn=0; fn<1024; fn++) {
- fm(0xA0,(fn & 0xFF));
- fm(0xB0,((fn >> 8) & 0x3) + (block << 2) | KEYON);
- delay(1);
- }
-
-
- /********************************************************************
- * Single tone again. Both channels, then if on stereo board, *
- * play tone in just the left channel, then just the right channel. *
- ********************************************************************/
-
- printf("440 Hz again, both channels.\n");
- block=4;
- fn=577; /* This number makes 440 Hz when block=4 and m=1 */
- fm(0xA0,(fn & 0xFF));
- fm(0xB0,((fn >> 8) & 0x3) + (block << 2) | KEYON);
-
- #ifdef STEREO
- #ifdef OPL3
- /* This left and right channel stuff is the only part of this program
- * that uses OPL3 mode. Everything else is available on the OPL2.
- */
-
- getche();
- printf("Left channel only\n");
- fm(0xC0,LEFT | 1); /* set left channel only, parallel connection */
-
- getche();
- printf("Right channel only\n");
- fm(0xC0,RIGHT | 1); /* set right channel only, parallel connection */
- #else
- getche();
- fm(0xB0,((fn >> 8) & 0x3) + (block << 2)); // key off
-
- printf("Left channel only\n");
- Profm1(0xB0,((fn >> 8) & 0x3) + (block << 2) | KEYON);
-
- getche();
- Profm1(0xB0,((fn >> 8) & 0x3) + (block << 2)); // key off
-
- printf("Right channel only\n");
- Profm2(0xB0,((fn >> 8) & 0x3) + (block << 2) | KEYON);
-
-
- #endif
- #endif
-
-
- /*********************************
- * Attenuate the signal by 3 dB. *
- *********************************/
-
- getche();
- fm(0xB0,((fn >> 8) & 0x3) + (block << 2) | KEYON);
- printf("Attenuated by 3 dB.\n");
- fm(0x43,4); /* attenuate by 3 dB */
- getche();
-
- fm(0xB0,((fn >> 8) & 0x3) + (block << 2));
-
- #ifdef OPL3
- /* Set OPL-3 back to OPL-2 mode, because if the next program to run was
- * written for the OPL-2, then it won't set the LEFT and RIGHT bits to
- * one, so no sound will be heard.
- */
- Profm2(5, 0); /* set back to OPL2 mode */
- #endif
- }
-